package com.tool.cn.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import com.tool.cn.model.SshModel;

/**
 * 
 * @author wangzonghui
 * @date 2022年1月19日 下午3:44:15 
 * @Description ssh工具类
 */
public class SshUtil {
	
	private final  Logger log = LoggerFactory.getLogger(this.getClass());

	
	/**
	 * 说明
	 * 1、不支持免密方式登录    由于jsch版本长久未升级，无法支持最新ssh加密，只能使用密码登录
	 * 2、上传文件有可能session连接失败    因为对方服务器配置sshd参数较小，文件：/etc/ssh/sshd_config  属性：MaxSessions 建议20个
	 * 3、new对象不支持同一时间，多文件上传（多线程上传），如需上传文件，必须new新对象，即一个文件一个new对象，上传速度不会因对象数量多增加，测试发现网络带宽一定，对象越多，所有文件上传时长同步增加。
	 * 
	 */
	
	
    public static void main(String[] args) throws Exception {
		String host = "172.16.34.160";
		int port = 22;
		String user = "用户名";
		String password = "密码";
    	SshUtil sshUtil = new SshUtil(host, user,password, port);
    	
    	sshUtil.putFile("文件","上传地址");
        
        String cmd="ls /root/wang/";
        System.out.println("返回结果:"+sshUtil.runCommand(cmd));
        
    }
    
    
	private String host;
	private String user;
	private String password;
	private int port;
	private Session session;

	/**
	 * 创建一个连接
	 * 
	 * @param host     地址
	 * @param user     用户名
	 * @param password 密码
	 * @param port     ssh2端口
	 */
	public SshUtil(String host, String user, String password, int port) {
		this.host = host;
		this.user = user;
		this.password = password;
		this.port = port;
	}
	
	/**
	 * 创建新对象
	 * @return
	 */
	public SshUtil newInit() {
		SshUtil sshUtil=new SshUtil(this.host,this.user,this.password,this.port);
		return sshUtil;
	}
	
	/**
	 * 登录测试
	 * @return
	 */
	public boolean loginTest() {
		try {
			initialSession();
		} catch (Exception e) {
			log.error("SSH Login Error"+e.toString(),e);
			return false;
		}
		
		try {
			close();
		} catch (Exception e) {
			log.error("Close SSH Error",e);
		}
		return true;
	}

	private void initialSession() throws Exception {
		if (session == null) {
			
			JSch jsch = new JSch();
			session = jsch.getSession(user, host, port);
			session.setUserInfo(new UserInfo() {

				public String getPassphrase() {
					return null;
				}

				public String getPassword() {
					return null;
				}

				public boolean promptPassword(String arg0) {
					return false;
				}

				public boolean promptPassphrase(String arg0) {
					return false;
				}

				public boolean promptYesNo(String arg0) {
					return true;
				}

				public void showMessage(String arg0) {
				}

			});
			session.setPassword(password);
			session.setTimeout(10000);
			session.connect();
		}
	}

	/**
	 * 关闭连接
	 * 
	 * @throws Exception
	 */
	public void close()  {
		if (session != null && session.isConnected()) {
			session.disconnect();
			session = null;
		}
	}
	
	/**
	 * 目录验证
	 * @param path
	 * @return
	 */
	public boolean checkPath(String path) {
		Channel channel=null;
		try {
			this.initialSession();
			channel = session.openChannel("sftp");
			channel.connect();
			ChannelSftp channelSftp = (ChannelSftp) channel;
			channelSftp.cd(path);
			
			return true;
		} catch (Exception e) {
			return false;
		}finally {
			try {
				if (channel!=null) {
					channel.disconnect();
				}
			} catch (Exception e) {
				log.error("Check Path "+path,e);
			}
		}
	}
	
	/**
	 * 文件验证是否存在
	 * @param path
	 * @return
	 */
	public boolean checkFile(String file) {
		Channel channel=null;
		try {
			this.initialSession();
			channel = session.openChannel("sftp");
			channel.connect();
			ChannelSftp channelSftp = (ChannelSftp) channel;
			channelSftp.ls(file);
			
			return true;
		} catch (Exception e) {
			return false;
		}finally {
			try {
				if (channel!=null) {
					channel.disconnect();
				}
			} catch (Exception e) {
				log.error("Check File "+file,e);
			}
		}
	}

	/**
	 * 上传文件
	 * 
	 * @param localFile  本地文件名，若为空或是*，表示目前下全部文件
	 * @param remotePath 远程路径，若为空，表示当前路径，若服务器上无此目录，则会自动创建
	 * @throws Exception
	 */
	public boolean putFile( String localFile, String remotePath) {
		Channel channel=null;
		try {
			this.initialSession();
			channel = session.openChannel("sftp");
			channel.connect();
			ChannelSftp channelSftp = (ChannelSftp) channel;
			String remoteFile = null;
			if (remotePath != null && remotePath.trim().length() > 0) {
				
				if(remotePath.contains("\\")) {    //window盘符转Linux
					remotePath=remotePath.replaceAll("\\\\", "/");
				}
				
				if(channelSftp.ls(remotePath)==null) {
					channelSftp.mkdir(remotePath);
				}
				
				remoteFile = remotePath + "/.";
			} else {
				remoteFile = ".";
			}
			
			channelSftp.put(localFile, remoteFile);
			
		} catch (Exception e) {
			log.error("Host:"+host+" User"+user+" Put File:"+localFile+" Error "+e.toString(), e);
			return false;
		}finally {
			try {
				if (channel!=null) {
					channel.disconnect();
				}
			} catch (Exception e) {
				log.error("Close Connection Error",e);
			}
		}

		return true;
	}

	/**
	 * 下载文件
	 * 
	 * @param localFile  本地文件名，若为空或是*，表示目前下全部文件
	 * @param remotePath  sftp待下载文件
	 * @throws Exception
	 */
	public boolean getFile( String localFile, String remoteFile) {
		Channel channel=null;
		try {
			this.initialSession();
			channel = session.openChannel("sftp");
			channel.connect();
			ChannelSftp channelSftp = (ChannelSftp) channel;

			if(channelSftp.ls(remoteFile)==null) {
				log.error("Sftp Path:{} No Exits",remoteFile);
				return false;
			}
			
			channelSftp.get(remoteFile, localFile);
			
		} catch (Exception e) {
			log.error("Host:"+host+" User"+user+"Get File:"+remoteFile+" Error: "+e.toString(), e);
			return false;
		}finally {
			try {
				if (channel!=null) {
					channel.disconnect();
				}
			} catch (Exception e) {
				log.error("Close Connection Error",e);
			}
		}

		return true;
	}
	
	/**
	 * 执行远程命令 
	 * @param command 执行的命令
	 * @return 0成功 1异常
	 * @throws Exception
	 */
	public boolean runCommand(String command)  {

		try {
			this.initialSession();
			InputStream in = null;
			InputStream err = null;
			BufferedReader inReader = null;
			BufferedReader errReader = null;
			int time = 0;
			String s = null;
			boolean run = false;

			ChannelExec channel = (ChannelExec) session.openChannel("exec");
			channel.setCommand(command);
			channel.setInputStream(null);
			channel.setErrStream(null);
			err = channel.getErrStream();
			in = channel.getInputStream();
			channel.connect();
			inReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
			errReader = new BufferedReader(new InputStreamReader(err, "UTF-8"));

			StringBuffer sb = new StringBuffer();
			sb.append("error: ");
			while (true) {
				s = errReader.readLine();
				if (s != null) {
					sb.append(s).append("\n");
				} else {
					run = true;
					break;
				}
			}
			
			sb.append("info: ");
			while (true) {
				s = inReader.readLine();
				if (s != null) {
					sb.append(s).append("\n");
				} else {
					run = true;
					break;
				}
			}

			while (true) {
				if (channel.isClosed() || run) {
					break;
				}
				try {
					Thread.sleep(100);
				} catch (Exception ee) {
				}
				if (time > 180) {

					break;
				}
				time++;
			}

			int status=channel.getExitStatus();
			inReader.close();
			errReader.close();
			channel.disconnect();
			
			String result=sb.toString();
			if(status>0) {
				log.info("{}  host:{} user:{} Run Is code:{} Error :{}",command,host,user,status,result);
			}
			return status==0?true:false;
		} catch (Exception e) {
			log.error(command+e.toString(),e);
			return false;
		}
	}
	
	/**
	 * 命令
	 * 
	 * @param command 命令
	 * @param outTime 超时时间 单位毫秒
	 * @apiNote 超时任务会继续执行，可按需求关闭实现类，或修改本方法stop任务线程
	 * @throws Exception
	 */
	public boolean runCommand(String command,int timeOut) {
		ProcessWithTimeout process=new ProcessWithTimeout("","",command,2);
		int exitCode=process.waitForProcess(timeOut);
		
		if(exitCode==-1) {
			log.error("Host:{} Run Command:{} Time:{} Out ",host,command,timeOut);
		}
		return exitCode==0?true:false;
	}
	
	/**
	 * 上传文件
	 * 
	 * @param localFile  本地文件名，若为空或是*，表示目前下全部文件
	 * @param remotePath 远程路径，若为空，表示当前路径，若服务器上无此目录，则会自动创建
	 * @param timeOut 超时时间单位毫秒
	 * 	@apiNote 超时任务会继续执行，可按需求关闭实现类，或修改本方法stop任务线程
	 * @throws Exception
	 */
	public boolean putFile( String localFile, String remotePath,int timeOut) {
		ProcessWithTimeout process=new ProcessWithTimeout(localFile,remotePath,"",0);
		int exitCode=process.waitForProcess(timeOut);
		
		if(exitCode==-1) {
			log.error("{} put Local File {} To Sftp Path:{} Time:{} Out",host,localFile,remotePath,timeOut);
		}
		return exitCode==0?true:false;
	}
	
	/**
	 * 下载文件
	 * 
	 * @param localFile  本地路径，若为空，表示当前路径
	 * @param remoteFile sftp待下载文件
	 * @param timeOut 超时时间单位毫秒
	 * @apiNote 超时任务会继续执行，可按需求关闭实现类，或修改本方法stop任务线程
	 * @throws Exception
	 */
	public boolean getFile( String localFile, String remoteFile,int timeOut) {
		ProcessWithTimeout process=new ProcessWithTimeout(localFile,remoteFile,"",1);
		int exitCode=process.waitForProcess(timeOut);
		
		if(exitCode==-1) {
			log.error("{} Save Sftp Path:{} To  Local  {}  Time:{} Out",host,remoteFile,localFile,timeOut);
		}
		return exitCode==0?true:false;
	}
	/**
	 * 
	 * @author wangzonghui
	 * @date 2022年4月13日 下午2:15:17 
	 * @Description 任务执行线程，判断操作超时使用
	 */
	class ProcessWithTimeout extends Thread{
		
		private String localFile;
		private String remoteFile;
		private String command;
		private int type;
		private int exitCode =-1;
		
		/**
		 * 
		 * @param localFile 本地文件
		 * @param remoteFile  sftp服务器文件
		 * @param type 0 上传  1 下载 2 执行命令
		 */
		public ProcessWithTimeout(String localFile, String remoteFile,String command,int type) {
			this.localFile=localFile;
			this.remoteFile=remoteFile;
			this.command=command;
			this.type=type;
		}
		
		public int waitForProcess(int outtime){
			  this.start();
			  
			  try{
			     this.join(outtime);
			  }catch (InterruptedException e){
			   log.error("Wait Is Error",e);
			  }
			  
			  return exitCode;
		 }
		
		@Override
		public void run() {
			super.run();
			boolean state;
			if(type==0) {
				state=putFile(localFile, remoteFile);
			}else if(type==1) {
				state=getFile(localFile, remoteFile);
			}else {
				state=runCommand(command);	
			}
			
			exitCode= state==true?0:1;
		}
	}
	
	/**
	 * 执行远程命令 
	 * @param command 执行的命令
	 * @return 结果信息类
	 */
	public SshModel command(String command)  {

		SshModel sshModel=new SshModel();
		try {
			this.initialSession();
			int time = 0;
			String s = "";
			boolean run = false;

			ChannelExec channel = (ChannelExec) session.openChannel("exec");
			channel.setCommand(command);
			channel.setInputStream(null);
			channel.setErrStream(null);
			InputStream err = channel.getErrStream();
			InputStream in = channel.getInputStream();
			channel.connect();
			BufferedReader inReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
			BufferedReader errReader = new BufferedReader(new InputStreamReader(err, "UTF-8"));

			StringBuffer sb = new StringBuffer();
			while (true) {
				s = errReader.readLine();
				if (s != null) {
					if(s.length()>0) {
						sb.append(s).append("\n");
					}
					
				} else {
					run = true;
					break;
				}
			}
			sshModel.setError(sb.toString());
			
			int num=0;
			sb = new StringBuffer();
			while (true) {
				s = inReader.readLine();
				if (s != null) {
					if(s.length()>0) {
						if(num!=0) {
							sb.append("|");
						}
						sb.append(s);
						num++;
					}
				} else {
					run = true;
					break;
				}
			}
			sshModel.setInfo(sb.toString());
			
			while (true) {
				if (channel.isClosed() || run) {
					break;
				}
				try {
					Thread.sleep(100);
				} catch (Exception ee) {
				}
				if (time > 180) {

					break;
				}
				time++;
			}

			int status=channel.getExitStatus();
			inReader.close();
			errReader.close();
			channel.disconnect();
			
			sshModel.setCode(status);
		} catch (Exception e) {
			log.error("Host:"+host+" User"+user+" Run Command "+command+" Error:"+e.toString(),e);
			sshModel.setCode(1);
		}
		return sshModel;
	}
	
	/**
	 * 执行远程命令 
	 * @param command 执行的命令
	 * @param timeOut 超时时间单位毫秒
	 * @apiNote 超时任务会继续执行，可按需求关闭实现类，或修改本方法stop任务线程
	 * @return 结果信息类
	 */
	public SshModel command(String command,int timeOut) {
		ProcesscommandTimeout process=new ProcesscommandTimeout(command);
		SshModel sshModel=process.waitForProcess(timeOut);
		
		if(sshModel==null) {
			log.error("Host:{} Run Command:{} Time:{} Out ",host,command,timeOut);
		}
		return sshModel;
	}
 
	/**
	 * 远程命令执行超时
	 * @author wangzonghui
	 * @date 2022-08-12 11:15:03
	 * @Description
	 */
	class ProcesscommandTimeout extends Thread{
		
		private String command;
		private SshModel sshModel=null;
		
		/**
		 * 
		 * @param localFile 本地文件
		 * @param remoteFile  sftp服务器文件
		 * @param type 0 上传  1 下载
		 */
		public ProcesscommandTimeout(String command) {
			this.command=command;
		}
		
		public SshModel  waitForProcess(int outtime){
			  this.start();
			  
			  try{
			     this.join(outtime);
			  }catch (InterruptedException e){
			   log.error("Wait Is Error",e);
			  }
			  
			  return sshModel;
		 }
		
		@Override
		public void run() {
			super.run();
			sshModel=command(command);
		}
	}
}